5 分で読了 #rust #Unsafe Rust
このページの目次

裸のポインタと FFI

*const T*mut T は借用チェッカーの制約を受けません——任意のエイリアシングが可能で、有効なメモリを指していないこともあります。これらが存在する唯一の理由は FFI です:C ライブラリの呼び出し、C コールバックの受信、ハードウェアレジスタの操作。unsafe ブロックは「安全チェックを無効にする」ものではなく、安全責任をコンパイラからプログラマへ移譲するものです。

裸のポインタ: borrow checker がないポインタ

let x = 42;
let ptr: *const i32 = &x;            // 参照 → 裸のポインタ (安全)
unsafe {
    println!("{}", *ptr);            // 間接参照 (unsafe — コンパイラは有効性を検証しない)
}

裸のポインタは borrow checker のルールに従う必要はありません——複数の *mut T が同じデータを指すことも可能で、null であることもあり、アライメントが保証されている必要もありません。すべての責任はプログラマに移譲されます。

C の int * との違い:Rust は *const T*mut T を区別します(C の const int *int * に類似)。ただし、C では const を暗黙的に破棄できますが、Rust ではできません。

extern "C": C 関数インターフェース

extern "C" {
    fn abs(input: i32) -> i32;       // C 関数の宣言
}

#[no_mangle]                         // Rust の名前マングリングを禁止
pub extern "C" fn rust_callback(x: i32) -> i32 {
    x + 1                            // C コードからこの関数を呼び出せる
}

extern "C" は C の呼び出し規約を使用します:引数はターゲットプラットフォームの C ABI に従ってレジスタまたはスタック上に配置されます。x86-64 ではこれは System V AMD64 ABI です(rdi, rsi, rdx, rcx, r8, r9 による引数渡し)。extern "system"(Windows API)や extern "Rust"(デフォルト)も使用できます。

#[repr(C)]: C 互換のメモリレイアウト

#[repr(C)]
struct MyStruct {
    a: i32,                          // オフセット 0
    // パディング: 4 バイト           // C では f64 を 8 バイト境界にアラインする必要がある
    b: f64,                          // オフセット 8
}                                    // サイズ: 16 バイト

#[repr(C)] を指定しない場合、Rust コンパイラは構造体のサイズを最適化するためにフィールドの順序を並べ替えることができます。#[repr(C)] を追加すると、フィールドは宣言順に配置され、C ABI の要件に従ってパディングが挿入され、C の構造体とのバイナリ互換性が保証されます。#[repr(C, packed)] を使用するとすべてのパディングが削除されます(ただし、未アラインの参照が発生する可能性があるため unsafe です)。

bindgen: Rust FFI バインディングの自動生成

C のヘッダファイルから Rust の extern 宣言、構造体定義、定数を生成します:

// build.rs:
fn main() {
    println!("cargo:rerun-if-changed=wrapper.h");
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .allowlist_function("my_lib_.*")   // これらの関数のみバインディングを生成
        .generate().expect("bindgen failed");
    bindings.write_to_file("src/bindings.rs").unwrap();
}
// src/lib.rs:
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

コールバック: C から Rust への呼び出し時の注意点

extern "C" fn callback(x: i32) {
    println!("C called with {}", x);
}
unsafe { register_callback(callback as unsafe extern "C" fn(i32)); }

Rust 関数をコールバックとして C に渡す場合、⁠パニックは FFI 境界を越えてはなりません⁠——コールバック内でパニックが発生し、アンワインドが C のスタックフレームを通過すると、未定義動作(UB)になります(C のスタックにはアンワインドテーブルがないため)。解決策として、コールバック内で std::panic::catch_unwind を使用してパニックをキャッチするか、Cargo.toml で panic = "abort" を設定します。

参考

  • Rustonomicon: FFI, C ABI
  • bindgen: rust-lang.github.io/rust-bindgen
  • The Embedded Rust Book: FFI and C interaction

*Keywords: raw pointer, *const, mut, extern "C", #[no_mangle], repr(C), bindgen, FFI callback, C ABI